﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using My.Common.AutoMapper;
using My.Common.Error;
using AutoMapper;
using My.Pattern.Builder;
using My.Common.Exception;
using My.Common.EntityHelper;
using System.Linq.Expressions;

namespace My.DataAccess.Core
{
    public class BaseRepository<DTO, DatabaseEntity> : IBaseRepository<DTO>
        where DTO : class, new()
        where DatabaseEntity : class, new()
    {
        private readonly string CreateDatePropertyName = "CreationDate";
        private readonly string CreatedByPropertyName = "CreatedBy";
        private readonly string LastUpdatedDatePropertyName = "LastUpdatedDate";
        private readonly string RecordTimestampPropertyName = "Timestamp";

        public IUnitOfWork UnitOfWork { get; set; }
        public IAutoMapperBuilder AutoMapperBuilder { get; set; }
        public IEntityHelper DTOHelper { get; set; }
        public IErrorLog ErrorLog { get; set; }
        public IErrorBuilder ErrorBuilder { get; set; }

        #region Virtual Mapping method

        #region Insert

        public virtual DTO Insert(DTO dto)
        {
            try
            {
                //SetCreationDate(dto);
                //SetCreatedBy(dto);
                var entity = ConvertDTOToEntity(dto);
                UnitOfWork.InsertOnSubmit(entity);
                UnitOfWork.SubmitChanges();
                return ConvertEntityToDTO(entity);
            }
            catch (Exception ex)
            {
                ErrorLog.WriteLog(ex);
                throw ex;
            }
        }

        #endregion

        #region Update

        public virtual DTO Update(DTO dto)
        {
            try
            {
                //SetLastUpdateDate(dto);
                var dtoId = DTOHelper.GetKeyValue(dto);
                var databaseEntity = GetDatabaseEntityBy(dtoId);
                if (databaseEntity == null)
                {
                    throw new ErrorException("Model",
                        ErrorBuilder.GetDescriptionFromErrorID(Error.COULD_NOT_FIND_DATA.ToString()));
                }

                UpdateInformationToDatabaseEntity(dto, databaseEntity);
                UnitOfWork.SubmitChanges();
                return ConvertEntityToDTO(databaseEntity);
            }
            catch (Exception ex)
            {
                ErrorLog.WriteLog(ex);
                throw ex;
            }
        }

        protected void UpdateInformationToDatabaseEntity(DTO dto, DatabaseEntity databaseEntity)
        {
            AutoMapperBuilder.MapSourceToDestination<DTO, DatabaseEntity>(dto, DefineIgnoredPropertyInMapping,
                databaseEntity);
        }

        private void DefineIgnoredPropertyInMapping(ConfigurationStore config)
        {
            var maps = config.CreateMap<DTO, DatabaseEntity>();
            var ignoredProperties = typeof(DatabaseEntity).GetProperties()
                .Where(x => x.Name == CreateDatePropertyName
                || x.Name == RecordTimestampPropertyName
                || x.Name == CreatedByPropertyName).ToList();

            foreach (var ignoredProperty in ignoredProperties)
            {
                maps = maps.ForMember(ignoredProperty.Name, x => x.Ignore());
            }
        }

        #endregion

        #region Delete

        public virtual void Delete(DTO dto)
        {
            try
            {
                var dtoId = DTOHelper.GetKeyValue(dto);
                var databaseEntity = GetDatabaseEntityBy(dtoId);
                UnitOfWork.DeleteOnSubmit(databaseEntity);
                UnitOfWork.SubmitChanges();
            }
            catch (Exception ex)
            {
                ErrorLog.WriteLog(ex);
                throw ex;
            }
        }

        #endregion

        #region Get

        public virtual DTO GetById(int dtoId)
        {
            try
            {
                var databaseEntity = GetDatabaseEntityBy(dtoId);
                if (databaseEntity != null)
                {
                    return ConvertEntityToDTO(databaseEntity);
                }

                return default(DTO);
            }
            catch (Exception ex)
            {
                ErrorLog.WriteLog(ex);
                throw ex;
            }
        }

        public virtual List<DTO> GetAll()
        {
            try
            {
                var query = UnitOfWork.Table<DatabaseEntity>()
                    .Select(x => ConvertEntityToDTO(x));
                if (query.Any())
                {
                    return query.ToList();
                }

                return null;
            }
            catch (Exception ex)
            {
                ErrorLog.WriteLog(ex);
                throw ex;
            }
        }

        #endregion

        protected virtual void UpdateKeyToDTO(DTO dto, DatabaseEntity entity)
        {
            var keyPropertyInfo = DTOHelper.GetKeyProperty(dto.GetType());
            var keyValue = (int)DTOHelper.GetValueOfProperty(entity, keyPropertyInfo.Name);
            DTOHelper.SetKeyValue(dto, keyValue);
        }

        protected virtual DatabaseEntity ConvertDTOToEntity(DTO dto)
        {
            return AutoMapperBuilder.MapSourceToDestination<DTO, DatabaseEntity>(dto);
        }

        protected virtual DTO ConvertEntityToDTO(DatabaseEntity entity)
        {
            return AutoMapperBuilder.MapSourceToDestination<DatabaseEntity, DTO>(entity);
        }

        #endregion

        #region Privates Methods

        private DatabaseEntity GetDatabaseEntityBy(int entityId)
        {
            var propertyName = DTOHelper.GetKeyName(typeof(DTO));

            var propertyInfo = DTOHelper.GetProperty(typeof(DatabaseEntity), propertyName);
            var param = Expression.Parameter(typeof(DatabaseEntity), "x");
            var propertyAccess = Expression.MakeMemberAccess(param, propertyInfo);
            var constantValue = Expression.Constant(entityId, typeof(int));
            var expression = Expression.Equal(propertyAccess, constantValue);
            var lambdaExpression = Expression.Lambda<Func<DatabaseEntity, bool>>(expression, param);
            return UnitOfWork.Table<DatabaseEntity>().FirstOrDefault(lambdaExpression);
        }

        //private void SetCreationDate(DTO dto)
        //{
        //    DTOHelper.SetValueOfProperty(dto, CreateDatePropertyName, DateTime.Now);
        //}

        //private void SetCreatedBy(DTO dto)
        //{
        //    DTOHelper.SetValueOfProperty(dto, CreatedByPropertyName, "system");
        //}

        //private void SetLastUpdateDate(DTO dto)
        //{
        //    DTOHelper.SetValueOfProperty(dto, LastUpdatedDatePropertyName, DateTime.Now);
        //}

        #endregion
    }
}
